From 31337913025090266926fd318e872e5d6f8dd101 Mon Sep 17 00:00:00 2001 From: Matthias Clasen Date: Tue, 3 Jan 2012 14:52:29 -0500 Subject: [PATCH] GtkApplication: Add logout notification This is fairly basic, allowing applications to learn when the session manager is about to end the session, and possibly block this. The only implementation at this point is using the org.gnome.SessionManager D-Bus interface of gnome-session. It should be straightforward to port the EggSMClient implementations for Windows and OS X. --- gtk/gtk.symbols | 1 + gtk/gtkapplication.c | 266 +++++++++++++++++++++++++++++++++++++++++++ gtk/gtkapplication.h | 10 +- 3 files changed, 276 insertions(+), 1 deletion(-) diff --git a/gtk/gtk.symbols b/gtk/gtk.symbols index 5129f86240..ece3c31559 100644 --- a/gtk/gtk.symbols +++ b/gtk/gtk.symbols @@ -224,6 +224,7 @@ gtk_application_get_menubar gtk_application_get_type gtk_application_get_windows gtk_application_new +gtk_application_quit_response gtk_application_remove_accelerator gtk_application_remove_window gtk_application_set_app_menu diff --git a/gtk/gtkapplication.c b/gtk/gtkapplication.c index adfd9279b1..7f3f252148 100644 --- a/gtk/gtkapplication.c +++ b/gtk/gtkapplication.c @@ -34,6 +34,7 @@ #include "gtkmain.h" #include "gtkaccelmapprivate.h" #include "gactionmuxer.h" +#include "gtkintl.h" #ifdef GDK_WINDOWING_QUARTZ #include "gtkquartz-menu.h" @@ -103,21 +104,36 @@ enum { WINDOW_ADDED, WINDOW_REMOVED, + QUIT_REQUESTED, + QUIT_CANCELLED, + QUIT, LAST_SIGNAL }; static guint gtk_application_signals[LAST_SIGNAL]; +enum { + PROP_ZERO, + PROP_REGISTER_SESSION +}; + G_DEFINE_TYPE (GtkApplication, gtk_application, G_TYPE_APPLICATION) struct _GtkApplicationPrivate { GList *windows; + gboolean register_session; + #ifdef GDK_WINDOWING_X11 GDBusConnection *session_bus; gchar *window_prefix; guint next_id; + + GDBusProxy *sm_proxy; + GDBusProxy *client_proxy; + gchar *app_id; + gchar *client_path; #endif #ifdef GDK_WINDOWING_QUARTZ @@ -187,6 +203,9 @@ window_prefix_from_appid (const gchar *appid) return appid_path; } +static void gtk_application_startup_session_dbus (GtkApplication *app); + + static void gtk_application_startup_x11 (GtkApplication *application) { @@ -195,6 +214,8 @@ gtk_application_startup_x11 (GtkApplication *application) application_id = g_application_get_application_id (G_APPLICATION (application)); application->priv->session_bus = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, NULL); application->priv->window_prefix = window_prefix_from_appid (application_id); + + gtk_application_startup_session_dbus (GTK_APPLICATION (application)); } static void @@ -203,6 +224,11 @@ gtk_application_shutdown_x11 (GtkApplication *application) g_free (application->priv->window_prefix); application->priv->window_prefix = NULL; g_clear_object (&application->priv->session_bus); + + g_clear_object (&application->priv->sm_proxy); + g_clear_object (&application->priv->client_proxy); + g_free (application->priv->app_id); + g_free (application->priv->client_path); } #endif @@ -481,12 +507,54 @@ gtk_application_notify (GObject *object, G_OBJECT_CLASS (gtk_application_parent_class)->notify (object, pspec); } +static void +gtk_application_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GtkApplication *application = GTK_APPLICATION (object); + + switch (prop_id) + { + case PROP_REGISTER_SESSION: + g_value_set_boolean (value, application->priv->register_session); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gtk_application_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GtkApplication *application = GTK_APPLICATION (object); + + switch (prop_id) + { + case PROP_REGISTER_SESSION: + application->priv->register_session = g_value_get_boolean (value); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + static void gtk_application_class_init (GtkApplicationClass *class) { GObjectClass *object_class = G_OBJECT_CLASS (class); GApplicationClass *application_class = G_APPLICATION_CLASS (class); + object_class->get_property = gtk_application_get_property; + object_class->set_property = gtk_application_set_property; object_class->notify = gtk_application_notify; application_class->add_platform_data = gtk_application_add_platform_data; @@ -534,6 +602,27 @@ gtk_application_class_init (GtkApplicationClass *class) NULL, NULL, g_cclosure_marshal_VOID__OBJECT, G_TYPE_NONE, 1, GTK_TYPE_WINDOW); + + gtk_application_signals[QUIT_REQUESTED] = + g_signal_new ("quit-requested", GTK_TYPE_APPLICATION, G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GtkApplicationClass, quit_requested), + NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0); + + gtk_application_signals[QUIT_CANCELLED] = + g_signal_new ("quit-cancelled", GTK_TYPE_APPLICATION, G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GtkApplicationClass, quit_cancelled), + NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0); + + gtk_application_signals[QUIT] = + g_signal_new ("quit", GTK_TYPE_APPLICATION, G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GtkApplicationClass, quit), + NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0); + + g_object_class_install_property (object_class, PROP_REGISTER_SESSION, + g_param_spec_boolean ("register-session", + P_("Register session"), + P_("Register with the session manager"), + FALSE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); } /** @@ -847,3 +936,180 @@ gtk_application_get_menubar (GtkApplication *application) return menubar; } + +/* D-Bus Session Management + * + * The protocol and the D-Bus API are described here: + * http://live.gnome.org/SessionManagement/GnomeSession + * http://people.gnome.org/~mccann/gnome-session/docs/gnome-session.html + */ + +#ifdef GDK_WINDOWING_X11 + +static void +unregister_client (GtkApplication *app) +{ + GError *error = NULL; + + g_debug ("Unregistering client"); + + g_dbus_proxy_call_sync (app->priv->sm_proxy, + "UnregisterClient", + g_variant_new ("(o)", app->priv->client_path), + G_DBUS_CALL_FLAGS_NONE, + G_MAXINT, + NULL, + &error); + + if (error) + { + g_warning ("Failed to unregister client: %s", error->message); + g_error_free (error); + } + + g_clear_object (&app->priv->client_proxy); + + g_free (app->priv->client_path); + app->priv->client_path = NULL; +} + +static void +client_proxy_signal (GDBusProxy *proxy, + const gchar *sender_name, + const gchar *signal_name, + GVariant *parameters, + GtkApplication *app) +{ + if (strcmp (signal_name, "QueryEndSession") == 0) + { + g_debug ("Received QueryEndSession"); + g_signal_emit (app, gtk_application_signals[QUIT_REQUESTED], 0); + } + else if (strcmp (signal_name, "EndSession") == 0) + { + g_debug ("Received EndSession"); + gtk_application_quit_response (app, TRUE, NULL); + unregister_client (app); + g_signal_emit (app, gtk_application_signals[QUIT], 0); + } + else if (strcmp (signal_name, "CancelEndSession") == 0) + { + g_debug ("Received CancelEndSession"); + g_signal_emit (app, gtk_application_signals[QUIT_CANCELLED], 0); + } + else if (strcmp (signal_name, "Stop") == 0) + { + g_debug ("Received Stop"); + unregister_client (app); + g_signal_emit (app, gtk_application_signals[QUIT], 0); + } +} + +static void +gtk_application_startup_session_dbus (GtkApplication *app) +{ + static gchar *client_id; + GError *error = NULL; + GVariant *res; + + if (app->priv->session_bus == NULL) + return; + + if (client_id == NULL) + { + const gchar *desktop_autostart_id; + + desktop_autostart_id = g_getenv ("DESKTOP_AUTOSTART_ID"); + /* Unset DESKTOP_AUTOSTART_ID in order to avoid child processes to + * use the same client id. + */ + g_unsetenv ("DESKTOP_AUTOSTART_ID"); + client_id = g_strdup (desktop_autostart_id ? desktop_autostart_id : ""); + } + + g_debug ("Connecting to session manager"); + + app->priv->sm_proxy = g_dbus_proxy_new_sync (app->priv->session_bus, + G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES | + G_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS, + NULL, + "org.gnome.SessionManager", + "/org/gnome/SessionManager", + "org.gnome.SessionManager", + NULL, + &error); + if (error) + { + g_warning ("Failed to get a session proxy: %s", error->message); + g_error_free (error); + return; + } + + /* FIXME: should we reuse the D-Bus application id here ? */ + app->priv->app_id = g_strdup (g_get_prgname ()); + + if (!app->priv->register_session) + return; + + g_debug ("Registering client '%s' '%s'", app->priv->app_id, client_id); + + res = g_dbus_proxy_call_sync (app->priv->sm_proxy, + "RegisterClient", + g_variant_new ("(ss)", app->priv->app_id, client_id), + G_DBUS_CALL_FLAGS_NONE, + G_MAXINT, + NULL, + &error); + + if (error) + { + g_warning ("Failed to register client: %s", error->message); + g_error_free (error); + g_clear_object (&app->priv->sm_proxy); + return; + } + + g_variant_get (res, "(o)", &app->priv->client_path); + g_variant_unref (res); + + g_debug ("Registered client at '%s'", app->priv->client_path); + app->priv->client_proxy = g_dbus_proxy_new_sync (app->priv->session_bus, 0, + NULL, + "org.gnome.SessionManager", + app->priv->client_path, + "org.gnome.SessionManager.ClientPrivate", + NULL, + &error); + if (error) + { + g_warning ("Failed to get client proxy: %s", error->message); + g_error_free (error); + g_clear_object (&app->priv->sm_proxy); + g_free (app->priv->client_path); + app->priv->client_path = NULL; + return; + } + + g_signal_connect (app->priv->client_proxy, "g-signal", G_CALLBACK (client_proxy_signal), app); +} + +void +gtk_application_quit_response (GtkApplication *application, + gboolean will_quit, + const gchar *reason) +{ + g_return_if_fail (GTK_IS_APPLICATION (application)); + g_return_if_fail (!g_application_get_is_remote (G_APPLICATION (application))); + g_return_if_fail (application->priv->client_proxy != NULL); + + g_debug ("Calling EndSessionResponse %d '%s'", will_quit, reason); + + g_dbus_proxy_call (application->priv->client_proxy, + "EndSessionResponse", + g_variant_new ("(bs)", will_quit, reason ? reason : ""), + G_DBUS_CALL_FLAGS_NONE, + G_MAXINT, + NULL, NULL, NULL); +} + +#endif diff --git a/gtk/gtkapplication.h b/gtk/gtkapplication.h index 2f87bb9b9c..aeb4688cbe 100644 --- a/gtk/gtkapplication.h +++ b/gtk/gtkapplication.h @@ -59,8 +59,12 @@ struct _GtkApplicationClass void (*window_removed) (GtkApplication *application, GtkWindow *window); + void (*quit_requested) (GtkApplication *application); + void (*quit_cancelled) (GtkApplication *application); + void (*quit) (GtkApplication *application); + /*< private >*/ - gpointer padding[14]; + gpointer padding[11]; }; GType gtk_application_get_type (void) G_GNUC_CONST; @@ -91,6 +95,10 @@ void gtk_application_remove_accelerator (GtkApplication *application const gchar *action_name, GVariant *parameter); +void gtk_application_quit_response (GtkApplication *application, + gboolean will_quit, + const gchar *reason); + G_END_DECLS #endif /* __GTK_APPLICATION_H__ */ -- 2.30.2